﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BEPUphysics;
using Microsoft.Xna.Framework;
using MagicArenaXNA.Games;
using BEPUphysics.DataStructures;
using BEPUphysics.CollisionShapes;
using BEPUphysics.MathExtensions;
using BEPUphysics.Collidables;
using BEPUphysics.CollisionShapes.ConvexShapes;
using Microsoft.Xna.Framework.Graphics;
using BEPUphysics.Entities.Prefabs;
using MagicArenaXNA.GameObject;
using BEPUphysicsDemos.AlternateMovement.SimpleCharacter;
using BEPUphysicsDrawer.Models;
using MagicArenaXNA.Assets;
using Networking;
using MagicArenaFramework.GameObject.Components;
using System.ComponentModel;
using MagicArenaFramework.GameObject.Components.ConcreteTypes;
using MagicArenaFramework.GameObject.Attributes;
using MagicArenaFramework.GameObject.Attributes.Concrete;
using MagicArenaXNA.GameObject.Spells;

namespace MagicArenaXNA.World
{
    class TestWorld : BepuWorld
    {
        PlayerLocalComp player;
        SimpleGameClient Client;
        UiBar UiBar;

        Dictionary<UInt64, PlayerRemoteComp> RemoteDict = new Dictionary<UInt64, PlayerRemoteComp>(5);

        GoComposite Scene = new ContainerComponent();
        GoComposite RemotePlayers = new ContainerComponent();

        public TestWorld(BepuGame game) : base(game)
        {

            Scene.AddChild(RemotePlayers);
            Scene.isActive = false;
            Game.IsMouseVisible = true;
            //Add networking client
            Client = new SimpleGameClient();
            Client.Start();

            //Load assets
            TestAssetHolder.Instance.LoadContent(game);

            // Add UiBar
            UiBar = new UiBar(game);
            Scene.AddChild(UiBar);

            // Add the floor
            ContainerComponent floor = new ContainerComponent();
            EntityComponent ent = new EntityComponent(new Box(new Vector3(0, -1, 0), 200, 1, 200), Space);
            floor.AddChild(ent);
            Scene.AddChild(floor);

            // Add local player
            player = new PlayerLocalComp(game, this, new Vector3(50,10,50));
            //PlayerIds.Add(player.PlayerId);
            RemoteDict.Add(player.PlayerId, null);

            //Set UI based on local player
            GoComponent comp;
            if (player.GetChild(typeof(ScalarAttributeHolder), out comp))
            {
                ScalarAttributeHolder att = (comp as ScalarAttributeHolder);
                UiBar.GetMyHealth = delegate()
                {
                    float cur, max;
                    if (att.ValueFor(CurHealthAttribute.CurHealthFamily, out cur) && att.ValueFor(MaxHealthAttribute.MaxHealthFamily, out max) && max!=0)
                    {
                        return cur / max;
                    }
                    return 0;
                };
                UiBar.GetMyMana = delegate()
                {
                    float cur, max;
                    if (att.ValueFor(CurManaAttribute.CurManaFamily, out cur) && att.ValueFor(MaxManaAttribute.MaxManaFamily, out max) && max != 0)
                    {
                        return cur / max;
                    }
                    return 0;
                };
            }
            else
            {
                //TODO do more appropriate error
                Console.WriteLine("Could not find attributes, oh no!");
            }
            if (player.GetChild(typeof(Targetter), out comp))
            {
                Targetter targ = (comp as Targetter);
                GoComponent comp2;
                GoComposite comp3;
                Type attType = typeof(ScalarAttributeHolder);
                ScalarAttributeHolder att;
                UiBar.GetTargetHealth = delegate()
                {
                    comp3 = targ.CurTargetComp as GoComposite;
                    if(comp3!=null && comp3.GetChild(attType, out comp2)){
                        att = (comp2 as ScalarAttributeHolder);
                        float cur, max;
                        if (att.ValueFor(CurHealthAttribute.CurHealthFamily, out cur) && att.ValueFor(MaxHealthAttribute.MaxHealthFamily, out max) && max != 0)
                        {
                            return cur / max;
                        }
                    }
                    return 0;
                };
                
            }
            // Set Ui spells whenever the bag changes type
            if (player.GetChild(typeof(SpellBag), out comp))
            {
                SpellBag bag = comp as SpellBag;
                bag.SpellChange += new SpellBag.OnSpellChange(
                    delegate(object sender, SpellBase[] newSpells)
                    {
                        UiBar.SetSpells(newSpells);
                    });
                UiBar.SetSpells(bag.Spells(bag.CurrentSchool));
            }


            Client.SendPlayer(player.PlayerId, "Player " + player.PlayerId);

            //Add player to Scene
            Scene.AddChild(player);
            Scene.isActive = true;

            //Add remote player
            PlayerRemoteComp newPlayer = new PlayerRemoteComp(Game, this, Vector3.Zero, Client, 0);
            RemoteDict.Add(0, newPlayer);
            newPlayer.isActive = true;
            Scene.AddChild(newPlayer);
            
             
        }

        public override string Name
        {
            get { return "TestWorld"; }
        }

        public override void DrawUI()
        {
            
        }

        public override void Draw()
        {
            Scene.Draw(prev_dt);

            Game.UIDrawer.Begin();
            Scene.OnGui(prev_dt);
            Game.UIDrawer.End();
        }

        private float prev_dt=0;
        private float ellapsed = 0;
        public override void Update(float dt)
        {
            base.Update(dt);
            prev_dt = dt;
            
            //Update networking
            Client.Update();
            // See if any new players joined, if so make them
            var packs = Client.PlayerInfoPackets;
            foreach (var p in packs)
            {
                if (!RemoteDict.ContainsKey(p.PlayerId))
                {
                    //New player
                    PlayerRemoteComp newPlayer = new PlayerRemoteComp(Game, this, Vector3.Zero, Client, p.PlayerId);
                    RemoteDict.Add(p.PlayerId, newPlayer);
                    newPlayer.isActive = true;
                    RemotePlayers.AddChild(newPlayer);
                }
                else
                {
                    GoComponent comp;
                    if (p.PlayerId != player.PlayerId)
                    {
                        PlayerRemoteComp oldPlayer;
                        RemoteDict.TryGetValue(p.PlayerId, out oldPlayer);
                        if (oldPlayer.GetChild(typeof(ScalarAttributeHolder), out comp)) (comp as ScalarAttributeHolder).AddUpdateValue(CurHealthAttribute.CurHealthFamily, p.Health);
                    }
                    else
                    {
                        if (player.GetChild(typeof(ScalarAttributeHolder), out comp)) (comp as ScalarAttributeHolder).AddUpdateValue(CurHealthAttribute.CurHealthFamily, p.Health);
                    }
                }
            }

            Scene.Update(dt);

            // If too much time has passed, send new PlayerInfo packet
            if (dt > 1)
            {
                float health=0;
                GoComponent comp;
                // Get the players health
                if (player.GetChild(typeof(ScalarAttributeHolder), out comp)) (comp as ScalarAttributeHolder).ValueFor(CurHealthAttribute.CurHealthFamily, out health);
                Client.SendPlayer(player.PlayerId, "Player " + player.PlayerId,(int)health);
                dt = 0;
            }

            // Send out position for this player
            Client.SendLocation(player.Entity.Position, player.Entity.Orientation, player.PlayerId);
            ellapsed += dt;
        }

        #region World setup methods
        void AddPlayer()
        {
            
        }
        #endregion
    }
}
